/*
 * File system server main loop -
 * serves IPC requests from other environments.
 */

#include <inc/x86.h>
#include <inc/string.h>

#include "fs.h"


#define debug 0

// The file system server maintains three structures
// for each open file.
//
// 1. The on-disk 'struct File' is mapped into the part of memory
//    that maps the disk.  This memory is kept private to the file
//    server.
// 2. Each open file has a 'struct Fd' as well, which sort of
//    corresponds to a Unix file descriptor.  This 'struct Fd' is kept
//    on *its own page* in memory, and it is shared with any
//    environments that have the file open.
// 3. 'struct OpenFile' links these other two structures, and is kept
//    private to the file server.  The server maintains an array of
//    all open files, indexed by "file ID".  (There can be at most
//    MAXOPEN files open concurrently.)  The client uses file IDs to
//    communicate with the server.  File IDs are a lot like
//    environment IDs in the kernel.  Use openfile_lookup to translate
//    file IDs to struct OpenFile.

struct OpenFile {
    uint32_t o_fileid;    // file id
    struct File *o_file;    // mapped descriptor for open file
    int o_mode;        // open mode
    struct Fd *o_fd;    // Fd page
};

// Max number of open files in the file system at once
#define MAXOPEN        1024
#define FILEVA        0xD0000000

// initialize to force into data section
struct OpenFile opentab[MAXOPEN] = {
        {0, 0, 1, 0}
};

// Virtual address at which to receive page mappings containing client requests.
union Fsipc *fsreq = (union Fsipc *) 0x0ffff000;

void
serve_init(void) {
    int i;
    uintptr_t va = FILEVA;
    for (i = 0; i < MAXOPEN; i++) {
        opentab[i].o_fileid = i;
        opentab[i].o_fd = (struct Fd *) va;
        va += PGSIZE;
    }
}

// Allocate an open file.
int
openfile_alloc(struct OpenFile **o) {
    int i, r;

    // Find an available open-file table entry
    for (i = 0; i < MAXOPEN; i++) {
        switch (pageref(opentab[i].o_fd)) {
            case 0:
                if ((r = sys_page_alloc(0, opentab[i].o_fd, PTE_P | PTE_U | PTE_W)) < 0)
                    return r;
                /* fall through */
            case 1:
                opentab[i].o_fileid += MAXOPEN;
                *o = &opentab[i];
                memset(opentab[i].o_fd, 0, PGSIZE);
                return (*o)->o_fileid;
        }
    }
    return -E_MAX_OPEN;
}

// Look up an open file for envid.
int
openfile_lookup(envid_t envid, uint32_t fileid, struct OpenFile **po) {
    struct OpenFile *o;

    o = &opentab[fileid % MAXOPEN];
    if (pageref(o->o_fd) <= 1 || o->o_fileid != fileid)
        return -E_INVAL;
    *po = o;
    return 0;
}

// Open req->req_path in mode req->req_omode, storing the Fd page and
// permissions to return to the calling environment in *pg_store and
// *perm_store respectively.
int
serve_open(envid_t envid, struct Fsreq_open *req,
           void **pg_store, int *perm_store) {
    char path[MAXPATHLEN];
    struct File *f;
    int fileid;
    int r;
    struct OpenFile *o;

    if (debug)
        cprintf("serve_open %08x %s 0x%x\n", envid, req->req_path, req->req_omode);

    // Copy in the path, making sure it's null-terminated
    memmove(path, req->req_path, MAXPATHLEN);
    path[MAXPATHLEN - 1] = 0;

    // Find an open file ID
    if ((r = openfile_alloc(&o)) < 0) {
        if (debug)
            cprintf("openfile_alloc failed: %e", r);
        return r;
    }
    fileid = r;

    // Open the file
    if (req->req_omode & O_CREAT) {
        if ((r = file_create(path, &f)) < 0) {
            if (!(req->req_omode & O_EXCL) && r == -E_FILE_EXISTS)
                goto try_open;
            if (debug)
                cprintf("file_create failed: %e", r);
            return r;
        }
    } else {
        try_open:
        if ((r = file_open(path, &f)) < 0) {
            if (debug)
                cprintf("file_open failed: %e", r);
            return r;
        }
    }

    // Truncate
    if (req->req_omode & O_TRUNC) {
        if ((r = file_set_size(f, 0)) < 0) {
            if (debug)
                cprintf("file_set_size failed: %e", r);
            return r;
        }
    }
    if ((r = file_open(path, &f)) < 0) {
        if (debug)
            cprintf("file_open failed: %e", r);
        return r;
    }

    // Save the file pointer
    o->o_file = f;

    // Fill out the Fd structure
    o->o_fd->fd_file.id = o->o_fileid;
    o->o_fd->fd_omode = req->req_omode & O_ACCMODE;
    o->o_fd->fd_dev_id = devfile.dev_id;
    o->o_mode = req->req_omode;

    if (debug)
        cprintf("sending success, page %08x\n", (uintptr_t) o->o_fd);

    // Share the FD page with the caller by setting *pg_store,
    // store its permission in *perm_store
    *pg_store = o->o_fd;
    *perm_store = PTE_P | PTE_U | PTE_W | PTE_SHARE;

    return 0;
}

// Set the size of req->req_fileid to req->req_size bytes, truncating
// or extending the file as necessary.
int
serve_set_size(envid_t envid, struct Fsreq_set_size *req) {
    struct OpenFile *o;
    int r;

    if (debug)
        cprintf("serve_set_size %08x %08x %08x\n", envid, req->req_fileid, req->req_size);

    // Every file system IPC call has the same general structure.
    // Here's how it goes.

    // First, use openfile_lookup to find the relevant open file.
    // On failure, return the error code to the client with ipc_send.
    if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
        return r;

    // Second, call the relevant file system function (from fs/fs.c).
    // On failure, return the error code to the client.
    return file_set_size(o->o_file, req->req_size);
}

// Read at most ipc->read.req_n bytes from the current seek position
// in ipc->read.req_fileid.  Return the bytes read from the file to
// the caller in ipc->readRet, then update the seek position.
// Returns the number of bytes successfully read, or < 0 on error.
//
// 从ipc->read.req_fileid中的“当前搜索位置”读取最多ipc->read.req_n字节。
// 将从文件中读到的字节，通过ipc->readRet返回给调用者，然后更新搜索位置。
// 返回成功读取的字节数，或者错误时返回< 0。
int
serve_read(envid_t envid, union Fsipc *ipc) {
    struct Fsreq_read *req = &ipc->read;
    struct Fsret_read *ret = &ipc->readRet;
    if (debug)
        cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n);

    // Lab 5: Your code here:
    int r;
    struct OpenFile *of;
    if (0 > (r = openfile_lookup(envid, req->req_fileid, &of))) {
        return r;
    }
    if (0 > (r = file_read(of->o_file, ret->ret_buf, req->req_n, of->o_fd->fd_offset))){
        return r;
    }
    of->o_fd->fd_offset += r;
    return r;
}


// Write req->req_n bytes from req->req_buf to req_fileid, starting at
// the current seek position, and update the seek position
// accordingly.  Extend the file if necessary.  Returns the number of
// bytes written, or < 0 on error.
//
// 将req->req_buf中的req->req_n个字节写入到req_fileid的文件，从当前寻址位置开始. 之后，相应地更新寻址位置。
// 如果需要的话，可以进行文件扩容。
// 返回写入的字节数，或者在错误时返回< 0。
int
serve_write(envid_t envid, struct Fsreq_write *req) {
    if (debug)
        cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n);

    // LAB 5: Your code here.
    struct OpenFile *of;
    int r;
    if (0 > (r = openfile_lookup(envid, req->req_fileid, &of))){
        return r;
    }
    if (0 > (r = file_write(of->o_file, req->req_buf, req->req_n, of->o_fd->fd_offset))){
        return r;
    }
    of->o_fd->fd_offset += r;
    return r;
}

// Stat ipc->stat.req_fileid.  Return the file's struct Stat to the
// caller in ipc->statRet.
int
serve_stat(envid_t envid, union Fsipc *ipc) {
    struct Fsreq_stat *req = &ipc->stat;
    struct Fsret_stat *ret = &ipc->statRet;
    struct OpenFile *o;
    int r;

    if (debug)
        cprintf("serve_stat %08x %08x\n", envid, req->req_fileid);

    if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
        return r;

    strcpy(ret->ret_name, o->o_file->f_name);
    ret->ret_size = o->o_file->f_size;
    ret->ret_isdir = (o->o_file->f_type == FTYPE_DIR);
    return 0;
}

// Flush all data and metadata of req->req_fileid to disk.
int
serve_flush(envid_t envid, struct Fsreq_flush *req) {
    struct OpenFile *o;
    int r;

    if (debug)
        cprintf("serve_flush %08x %08x\n", envid, req->req_fileid);

    if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
        return r;
    file_flush(o->o_file);
    return 0;
}


int
serve_sync(envid_t envid, union Fsipc *req) {
    fs_sync();
    return 0;
}

typedef int (*fshandler)(envid_t envid, union Fsipc *req);

fshandler handlers[] = {
        // Open is handled specially because it passes pages
        /* [FSREQ_OPEN] =	(fshandler)serve_open, */
        [FSREQ_READ] =        serve_read,
        [FSREQ_STAT] =        serve_stat,
        [FSREQ_FLUSH] =        (fshandler) serve_flush,
        [FSREQ_WRITE] =        (fshandler) serve_write,
        [FSREQ_SET_SIZE] =    (fshandler) serve_set_size,
        [FSREQ_SYNC] =        serve_sync
};

void
serve(void) {
    uint32_t req, whom;
    int perm, r;
    void *pg;

    while (1) {
        perm = 0;
        req = ipc_recv((int32_t *) &whom, fsreq, &perm);
        if (debug)
            cprintf("fs req %d from %08x [page %08x: %s]\n",
                    req, whom, uvpt[PGNUM(fsreq)], fsreq);

        // All requests must contain an argument page
        if (!(perm & PTE_P)) {
            cprintf("Invalid request from %08x: no argument page\n",
                    whom);
            continue; // just leave it hanging...
        }

        pg = NULL;
        if (req == FSREQ_OPEN) {
            r = serve_open(whom, (struct Fsreq_open *) fsreq, &pg, &perm);
        } else if (req < ARRAY_SIZE(handlers) && handlers[req]) {
            r = handlers[req](whom, fsreq);
        } else {
            cprintf("Invalid request code %d from %08x\n", req, whom);
            r = -E_INVAL;
        }
        ipc_send(whom, r, pg, perm);
        sys_page_unmap(0, fsreq);
    }
}

void
umain(int argc, char **argv) {
    static_assert(sizeof(struct File) == 256);
    binaryname = "fs";
    cprintf("FS is running\n");

    // Check that we are able to do I/O
    outw(0x8A00, 0x8A00);
    cprintf("FS can do I/O\n");

    serve_init();
    fs_init();
    fs_test();
    serve();
}

